package im.composer.midi.ip;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.List;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;

class IPMidiDevice implements MidiDevice,Runnable,Receiver {

	static final IPMidiDevice[] devices = new IPMidiDevice[] { new IPMidiDevice(21928), new IPMidiDevice(21929), new IPMidiDevice(21930) };

	private final int port;
	private DatagramSocket socket;
	private boolean running = false;
	private long start_at;
	private Info info;
	private Transmitter tx;

	private IPMidiDevice(int port) {
		this.port = port;
		try {
			Constructor<Info> con = Info.class.getDeclaredConstructor(String.class,String.class,String.class,String.class);
			con.setAccessible(true);
			info = con.newInstance("ipMIDI","zdl.hk","ipMIDI port:"+port,"0.0.1");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public Info getDeviceInfo() {
		return info;
	}
	
	DatagramSocket getSocket(){
		return socket;
	}

	@Override
	public void open() throws MidiUnavailableException {
		try {
			socket = new DatagramSocket(port);
		} catch (SocketException e) {
			throw new MidiUnavailableException(e.getLocalizedMessage());
		}
		start_at = System.currentTimeMillis();
		Thread thread = new Thread(this,info.getDescription());
		thread.setDaemon(true);
		running = true;
		thread.start();
	}

	@Override
	public void run() {
		byte[] buf = new byte[10];
		DatagramPacket p = new DatagramPacket(buf,buf.length);
		MidiMessage msg;
		try {
			socket.setSoTimeout(100);
		} catch (SocketException e1) {
		}
		while(running){
			try {
				socket.receive(p);
				byte[] data = new byte[p.getLength()];
				System.arraycopy(buf, 0, data, 0, p.getLength());
				msg = new RawMidiMessage(data);
				getTransmitter().getReceiver().send(msg, getMicrosecondPosition());
			} catch (Exception e) {
			}
		}
	}

	@Override
	public void close() {
		running = false;
		if (socket != null) {
			socket.close();
		}
	}

	@Override
	public boolean isOpen() {
		return running && socket != null && socket.isBound();
	}

	@Override
	public long getMicrosecondPosition() {
		if (isOpen()) {
			return 1000*(System.currentTimeMillis() - start_at);
		}
		return 0;
	}

	@Override
	public void send(MidiMessage message, long timeStamp) {
		if(!isOpen()){
			throw new java.lang.IllegalStateException("Socket Closed!");
		}
		byte[] data = message.getMessage();
		DatagramPacket p = new DatagramPacket(data,data.length);
		p.setPort(socket.getLocalPort());
		try {
			p.setAddress(InetAddress.getByName("255.255.255.255"));
			socket.send(p);
		} catch (IOException e) {
		}
	}

	@Override
	public int getMaxReceivers() {
		return -1;
	}

	@Override
	public int getMaxTransmitters() {
		return -1;
	}

	@Override
	public Receiver getReceiver() throws MidiUnavailableException {
		return this;
	}

	@Override
	public List<Receiver> getReceivers() {
		throw new java.lang.UnsupportedOperationException("Not Implemented Yet!");
	}

	@Override
	public Transmitter getTransmitter() throws MidiUnavailableException {
		if(tx==null){
			tx = new IPMidiTransmitter();
		}
		return tx;
	}

	@Override
	public List<Transmitter> getTransmitters() {
		throw new java.lang.UnsupportedOperationException("Not Implemented Yet!");
	}

}
